/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.itu.modernai;

/**
 * evolve: kill / death ratio measure of how good a gene is: (kills/deaths)
 * (fitness)
 *
 * health: ammo: engage distance:
 */
import cz.cuni.amis.pogamut.base.agent.navigation.IPathExecutorState;
import cz.cuni.amis.pogamut.base.utils.guice.AgentScoped;
import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
import cz.cuni.amis.pogamut.ut2004.agent.module.sensomotoric.Weapon;
import cz.cuni.amis.pogamut.ut2004.agent.module.utils.TabooSet;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.UT2004PathAutoFixer;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.stuckdetector.UT2004DistanceStuckDetector;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.stuckdetector.UT2004PositionStuckDetector;
import cz.cuni.amis.pogamut.ut2004.agent.navigation.stuckdetector.UT2004TimeStuckDetector;
import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004BotModuleController;
import cz.cuni.amis.pogamut.ut2004.communication.messages.ItemType;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.AddInventory;
import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Rotate;

import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.*;
import cz.cuni.amis.pogamut.ut2004.utils.UT2004BotRunner;
import cz.cuni.amis.utils.exception.PogamutException;
import cz.cuni.amis.utils.flag.FlagListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

enum STATE {

    HEALTH, AMMO, ENGAGE, WEAPON, ROAM, ESCAPE, PURSUE, OTHER
};

/**
 * @authors Christoffer Krogslund, Lasse Honnens & Anders Mousten
 * This java file is a controller for Unreal Tournament using a Genetic Algorithm
 * for decision making, along with a Artificial Neural Network for choosing the best weapon
 * at a given time.
 */


@AgentScoped
public class GABot extends UT2004BotModuleController {

    /**
     * The AmmoLists class contain information of all the ammo types relevant for the bot
     * in the given level.
     */
    class AmmoLists {
        List<Item> ShieldGunAmmo;
        List<Item> AssaultRifleAmmo;
        List<Item> BioRifleAmmo;
        List<Item> ShockRifleAmmo;
        List<Item> LinkGunAmmo;
        List<Item> MiniGunAmmo;
        List<Item> FlakCannonAmmo;
        List<Item> RocketLauncherAmmo;
        List<Item> SniperRifleAmmo;
        List<Item> LightningGunAmmo;
        
        /**
         * buildLists() builds all the ammo lists based on their related type.
         */
        public void buildLists() {
            ShieldGunAmmo = new ArrayList<Item>();
            AssaultRifleAmmo = new ArrayList<Item>();
            BioRifleAmmo = new ArrayList<Item>();
            ShockRifleAmmo = new ArrayList<Item>();
            LinkGunAmmo = new ArrayList<Item>();
            MiniGunAmmo = new ArrayList<Item>();
            FlakCannonAmmo = new ArrayList<Item>();
            RocketLauncherAmmo = new ArrayList<Item>();
            SniperRifleAmmo = new ArrayList<Item>();
            LightningGunAmmo = new ArrayList<Item>();

            //new test...
            log.log(Level.INFO, "Building ammo lists for bot: {0} for the first time", bot.getName());
            for (Item ammoItem : items.getSpawnedItems(ItemType.Category.AMMO).values()) {
                if (ammoItem.getType() == ItemType.SHIELD_GUN_AMMO) {
                    ShieldGunAmmo.add(ammoItem);
                } else if (ammoItem.getType() == ItemType.ASSAULT_RIFLE_AMMO) {
                    AssaultRifleAmmo.add(ammoItem);
                } else if (ammoItem.getType() == ItemType.BIO_RIFLE_AMMO) {
                    BioRifleAmmo.add(ammoItem);
                } else if (ammoItem.getType() == ItemType.SHOCK_RIFLE_AMMO) {
                    ShockRifleAmmo.add(ammoItem);
                } else if (ammoItem.getType() == ItemType.LINK_GUN_AMMO) {
                    LinkGunAmmo.add(ammoItem);
                } else if (ammoItem.getType() == ItemType.MINIGUN_AMMO) {
                    MiniGunAmmo.add(ammoItem);
                } else if (ammoItem.getType() == ItemType.FLAK_CANNON_AMMO) {
                    FlakCannonAmmo.add(ammoItem);
                } else if (ammoItem.getType() == ItemType.ROCKET_LAUNCHER_AMMO) {
                    RocketLauncherAmmo.add(ammoItem);
                } else if (ammoItem.getType() == ItemType.SNIPER_RIFLE_AMMO) {
                    SniperRifleAmmo.add(ammoItem);
                } else if (ammoItem.getType() == ItemType.LIGHTNING_GUN_AMMO) {
                    LightningGunAmmo.add(ammoItem);
                }
            }
            log.log(Level.INFO, "ammo lists for bot: {0} has been built!", bot.getName());
        }

        /**
         * refreshAmmoList refreshes the class variables (lists) and filters the items.
         * @param currentWeapon The weapon that the bot is currently using.
         * @return The nearest item related to the currentWeapon variable.
         */
        public Item refreshAmmoList(ItemType currentWeapon) {
            log.log(Level.INFO, "Refreshing {0} ammo lists for bot: {1}!", new Object[]{currentWeapon.toString(), bot.getName()});
            //body.getCommunication().sendGlobalTextMessage(currentWeapon.getName());

            //body.getCommunication().sendGlobalTextMessage("I AM LOOKING FOR "+currentWeapon.getName() +" AMMO!!!");
            Set<Item> ammoItems;


            if (currentWeapon == ItemType.ASSAULT_RIFLE) {
                ammoItems = tabooItems.filter(AssaultRifleAmmo);
                //body.getCommunication().sendGlobalTextMessage("size: " + ammoItems.size());
                if (!ammoItems.isEmpty()) {
                    //body.getCommunication().sendGlobalTextMessage("size: " + ammoItems.size());
                    return fwMap.getNearestItem(ammoItems, info.getNearestNavPoint());
                }
            } else if (currentWeapon == ItemType.BIO_RIFLE) {
                ammoItems = tabooItems.filter(BioRifleAmmo);
                if (!ammoItems.isEmpty()) 
                    return fwMap.getNearestItem(ammoItems, info.getNearestNavPoint());

            } else if (currentWeapon == ItemType.FLAK_CANNON) {
                ammoItems = tabooItems.filter(FlakCannonAmmo);
                if (!ammoItems.isEmpty()) 
                    return fwMap.getNearestItem(ammoItems, info.getNearestNavPoint());

            } else if (currentWeapon == ItemType.LIGHTNING_GUN) {
                ammoItems = tabooItems.filter(LightningGunAmmo);
                if (!ammoItems.isEmpty()) 
                    return fwMap.getNearestItem(ammoItems, info.getNearestNavPoint());

            } else if (currentWeapon == ItemType.LINK_GUN) {
                ammoItems = tabooItems.filter(LinkGunAmmo);
                if (!ammoItems.isEmpty()) 
                    return fwMap.getNearestItem(ammoItems, info.getNearestNavPoint());

            } else if (currentWeapon == ItemType.MINIGUN) {
                ammoItems = tabooItems.filter(MiniGunAmmo);
                if (!ammoItems.isEmpty()) 
                    return fwMap.getNearestItem(ammoItems, info.getNearestNavPoint());
            
            } else if (currentWeapon == ItemType.ROCKET_LAUNCHER) {
                ammoItems = tabooItems.filter(RocketLauncherAmmo);
                if (!ammoItems.isEmpty()) 
                    return fwMap.getNearestItem(ammoItems, info.getNearestNavPoint());
                
            } else if (currentWeapon == ItemType.SHIELD_GUN) {
                ammoItems = tabooItems.filter(ShieldGunAmmo);
                if (!ammoItems.isEmpty()) 
                    return fwMap.getNearestItem(ammoItems, info.getNearestNavPoint());
                
            } else if (currentWeapon == ItemType.SHOCK_RIFLE) {
                ammoItems = tabooItems.filter(ShockRifleAmmo);
                if (!ammoItems.isEmpty()) 
                    return fwMap.getNearestItem(ammoItems, info.getNearestNavPoint());
                
            } else if (currentWeapon == ItemType.SNIPER_RIFLE) {
                ammoItems = tabooItems.filter(SniperRifleAmmo);
                if (!ammoItems.isEmpty()) 
                    return fwMap.getNearestItem(ammoItems, info.getNearestNavPoint());
                
            }

            log.log(Level.INFO, "{0} ammo list for bot: {1} returned null...!", new Object[]{currentWeapon.getName(), bot.getName()});
            reset();
            return null;
        }
    }
    //Variable for improved aiming
    private boolean useImprovedAim = true;
    //Variables for neural weapon selection
    private Map<WeapWithFireMode, TrainedANN> neuralMap = new HashMap<WeapWithFireMode, TrainedANN>();
    private WeapWithFireMode bestWeap = new WeapWithFireMode(ItemType.SHIELD_GUN, true);
    private double eDist = 0, eVel = 0;
    //looking for states:
    String name;
    //Variables for stats
    private Map<Weapon, Integer[]> shotsFired;
    private GeneManager geneManager;
    private Gene m_Gene;
    private List<Item> m_HealthVialsSpotted;
    //state variables
    private AmmoLists ammoLists;
    private STATE currState = STATE.OTHER;
    private STATE lastState = STATE.OTHER;
    private List<Item> m_ShieldsSpotted;
    private List<Item> m_AdrenalineSpotted;
    private List<Item> m_WeaponsSpotted;
    private List<STATE> m_States;
    private List<Item> m_allItems = null;
    private Item item;
    protected TabooSet<Item> tabooItems = null;
    Player enemy;
    private Location lastEnemyLoc = new Location(0, 0, 0);
    int notMoving = 0;
    private UT2004PathAutoFixer autoFixer;
    //Variables for stuck in area detector
    private boolean stuck = true;
    private int updateCount = 0;
    private Location stuckLoc;
    
    
    

    @Override
    public void prepareBot(UT2004Bot bot) {
        tabooItems = new TabooSet<Item>(bot);

        shotsFired = new HashMap<Weapon, Integer[]>();
        //geneManager = GeneManager.getInstance();
        try{geneManager = GeneManager.FromTextFile("runs/G15top.txt");}
        catch(Exception e){body.getCommunication().sendGlobalTextMessage("Failed to load from textfile: " + e.getLocalizedMessage());}
        ammoLists = new AmmoLists();

        m_Gene = geneManager.getNextGene(body);
        
        m_HealthVialsSpotted = new ArrayList<Item>();
        m_ShieldsSpotted = new ArrayList<Item>();
        m_AdrenalineSpotted = new ArrayList<Item>();
        m_WeaponsSpotted = new ArrayList<Item>();
        m_States = InitializeStatePriority();
        // By uncommenting following line, you can make the bot to do the file logging of all its components
        //bot.getLogger().addDefaultFileHandler(new File("EmptyBot.log"));
        currState = STATE.OTHER;

        loadNeuralNets();
        // listeners        
        pathExecutor.addStuckDetector(new UT2004TimeStuckDetector(bot, 3000, 10000)); // if the bot does not move for 3 seconds, considered that it is stuck
        pathExecutor.addStuckDetector(new UT2004PositionStuckDetector(bot)); // watch over the position history of the bot, if the bot does not move sufficiently enough, consider that it is stuck
        pathExecutor.addStuckDetector(new UT2004DistanceStuckDetector(bot));

        pathExecutor.getState().addListener(new FlagListener<IPathExecutorState>() {
            @Override
            public void flagChanged(IPathExecutorState changedValue) {
                switch (changedValue.getState()) {
                    case PATH_COMPUTATION_FAILED:
                    case STUCK:
                        if (item != null) {
                            //body.getCommunication().sendGlobalTextMessage("added " + item.getType() + " to tabooItems!");
                            tabooItems.add(item, 10);
                        }
                        reset();
                        break;

                    case TARGET_REACHED:
                        reset();
                        break;
                }
            }
        });
        autoFixer = new UT2004PathAutoFixer(bot, pathExecutor, fwMap, navBuilder);     
    }

    @Override
    public void botFirstSpawn(GameInfo gameInfo, ConfigChange currentConfig, InitedMessage init, Self self) {
        name = info.getName();
    
        m_Gene.setName(name);
        ammoLists.buildLists();
        ScanMap();

        for (int i = 0; i < GeneManager.POPULATION_SIZE; i++) 
            log.log(Level.INFO, "{0}", geneManager.getGene(i).toString());
        
        
    }

    @Override
    public void botKilled(BotKilled event) {

        //body.getCommunication().sendGlobalTextMessage("" + stats.getDeaths());
        if (stats.getDeaths() == 7000) {
            //body.getCommunication().sendGlobalTextMessage("I WILL HAS A NEW GENE!!!" + m_Gene.getChromosomeElement(0));
            //float rate = ((float)stats.getKilledOthers() / (float)stats.getDeaths());
            m_Gene.setKillsAndDeaths((float)stats.getKilledOthers(), (float)stats.getDeaths());
            m_Gene.updateFitness();
            body.getCommunication().sendGlobalTextMessage(""+(float)m_Gene.getFitness());
            
            m_Gene = geneManager.getNextGene(body);
            m_Gene.setName(name);
            stats.resetStats();
            //body.getCommunication().sendGlobalTextMessage("NEW GENE HAS!!!" + m_Gene.getChromosomeElement(0));
        }

        lastState = STATE.OTHER;
        currState = STATE.OTHER;
        m_allItems = null;
        item = null;
    }

    private void UpdateShotsFired(Weapon weapon) {
        if (shotsFired.containsKey(weapon)) 
            shotsFired.get(weapon)[0] = shotsFired.get(weapon)[0] + 1;
        else
            shotsFired.put(weapon, new Integer[]{1, 0});
        
    }

    private void UpdateWeaponKills(Weapon weapon) {
        if (shotsFired.containsKey(weapon)) 
            shotsFired.get(weapon)[1]++;
        else 
            throw new NullPointerException("One can not kill an enemy without using a weapon!");
        
        stats.getDeaths();
    }
    
    private boolean lookingForAWeapon(){
        for(ItemType weapon : ItemType.Category.WEAPON.getTypes()){
            if(weaponry.hasLoadedWeapon(weapon) && weapon != ItemType.ASSAULT_RIFLE && weapon != ItemType.SHIELD_GUN){
                //body.getCommunication().sendGlobalTextMessage("I have other weapons than ASSRIFLE and SHHGUN!");
                return false;
            }
        }
        return true;
    }

    /**
     * Decides via the Genetic Algorithm which state to take.
     */
    @Override
    public void logic() throws PogamutException {
        //Check if there is a visible players, if not keep the same target.
        if (!players.getVisiblePlayers().values().isEmpty()) 
            enemy = players.getNearestVisiblePlayer(players.getVisiblePlayers().values());
        

        //if we are not moving for some time, then reset the variables.
        if (!info.isMoving()) {
            ++notMoving;
            if (notMoving > 4) {
                reset();
                return;
            }
        }
        /**
         * Checks if we are stuck in an area
         * @param areaRadius radius of area to check in (unreal units)
         * @param updates number of logic updates to check for
         */
        if(stuckInArea(200, 30)){ 
            reset();
            return;
        }
        
        boolean lookingForWeapon = lookingForAWeapon();

        ChooseState();
        
        if(lookingForWeapon)
            if(currState != STATE.ENGAGE || currState != STATE.HEALTH && lastState == STATE.ROAM)
                currState = STATE.WEAPON;
        
                
        //Are we being hit, then turn around
        if (senses.isBeingDamaged() && currState != STATE.ENGAGE)
            getAct().act(new Rotate().setAmount(32000));

        //Are we shooting? Stop it... (Prevents bot from emptying its weapon without anything to shoot at...
        if (info.isShooting() && currState != STATE.ENGAGE)
            shoot.stopShooting();
        
        if (currState == STATE.AMMO && lastState == STATE.AMMO) {
            config.setName(name + " [AMMO] : " + weaponry.getCurrentWeapon().getType().getName());
            
            if(weaponry.getCurrentWeapon().getType() == ItemType.ASSAULT_RIFLE ){
                for(ItemType weapon : ItemType.Category.WEAPON.getTypes()){
                    if(weaponry.hasLoadedWeapon(weapon) && weapon != ItemType.ASSAULT_RIFLE && weapon != ItemType.SHIELD_GUN){
                        //body.getCommunication().sendGlobalTextMessage("change weapon to " + weapon.getName());
                        weaponry.changeWeapon(weapon);
                    } else {
                        currState = STATE.WEAPON;
                        item = GetNearest(ItemType.Category.WEAPON);
                        if(item == null)
                            currState = STATE.ROAM;
                        else {
                            //body.getCommunication().sendGlobalTextMessage("Navigate to weapon: "+weapon.getName());
                            navigation.navigate(item);
                        }
                    }
                }
            }
        }    else if (currState == STATE.AMMO && lastState != STATE.AMMO) {
                item = GetNearestAmmo(weaponry.getCurrentWeapon().getDescriptor().getPriAmmoItemType());//navigation.navigate(ammoLists.refreshAmmoList(weaponry.getCurrentWeapon().getType()));
                if (item != null) 
                    navigation.navigate(item);
                else{
                    //body.getCommunication().sendGlobalTextMessage("No ammo to navigate to! Switching to ROAM");
                    currState = STATE.ROAM;
                }
            }

            //getAmmo();
        
        if (currState == STATE.HEALTH) {
            config.setName(name + " [HEALTH]");
            /*Player enemy = players.getNearestVisiblePlayer(players.getVisibleEnemies().values());
             if(enemy!=null){
             double eDist2 = info.getLocation().getDistance(enemy.getLocation());
             double eVel2 = enemy.getVelocity().size();
             WeapWithFireMode bestWeap2 = chooseWeapon(eDist2, eVel2);
             weaponry.changeWeapon(bestWeap2.item);
             }*/ // The commented block continuously changing his weapon based on the neural networks
            if (lastState != STATE.HEALTH) {
                item = NavigateToNearestItem(ItemType.Category.HEALTH);
                if (item != null) 
                    navigation.navigate(item);
            }
        }
        
        if (currState == STATE.ROAM) {
            config.setName(name + " [ROAM]");
            Roam();
        }
        if (currState == STATE.ENGAGE) {
            config.setName(name + " [ENGAGE]");
            Shoot();
        }
        if (currState == STATE.ESCAPE) {
            config.setName(name + " [ESCAPE]");
            enemy = players.getNearestVisiblePlayer(players.getVisibleEnemies().values());
            if (enemy != null) {
                lastEnemyLoc = enemy.getLocation();
                move.moveTo(Location.add(info.getLocation(), Location.sub(info.getLocation(), enemy.getLocation())));
            } else 
                move.turnTo(lastEnemyLoc); // This should make the bot see the enemy, so that he can move away from him next update
            
        }

        if (enemy != null && currState == STATE.PURSUE) {
            config.setName(name + " [PURSUE]");
            log.log(Level.INFO, "PURSUE [{0}]{1}, {2}", new Object[]{lastState, pursueCount, m_Gene.getChromosomeElement(5)});
            Pursue(enemy, m_Gene.getChromosomeElement(5));
        }
        
        if(currState == STATE.WEAPON){
            config.setName(name + " [WEAPON]");
            item = GetNearest(ItemType.Category.WEAPON);
            if(item == null)
                currState = STATE.ROAM;
            else {
                //body.getCommunication().sendGlobalTextMessage("Navigate to weapon (via WEAPON STATE): "+item.getType().getName());
                navigation.navigate(item);
            }
        }

        lastState = currState;
        //body.getCommunication().sendGlobalTextMessage("01currState: " + currState + ", lastState: " + lastState);
    }

    void reset() {
        lastState = STATE.OTHER;
        currState = STATE.OTHER;
        item = null;
        m_allItems = null;
        pathExecutor.stop();
        notMoving = 0;
    }

    protected void getAmmo() {
        //body.getCommunication().sendGlobalTextMessage(weaponry.getCurrentWeapon().getDescriptor().getPriAmmoType());
        if (lastState != STATE.AMMO) {
            navigation.navigate(ammoLists.refreshAmmoList(weaponry.getCurrentWeapon().getDescriptor().getPriAmmoItemType()));
        }
    }

    protected void Roam() {
/*        weaponry.changeWeapon(ItemType.SHIELD_GUN);
        if(weaponry.hasAmmoForWeapon(ItemType.SHIELD_GUN))
            shoot.shootSecondary();*/
        if (lastState != STATE.ROAM) {
            m_allItems = new LinkedList<Item>(items.getSpawnedItems().values());
            Set<Item> allItems = tabooItems.filter(m_allItems);
            if (allItems.isEmpty()) {
                //body.getCommunication().sendGlobalTextMessage(("NNO ITEMSSSSSS!"));
                reset();
                return;
            }

            item = m_allItems.iterator().next();
            pathExecutor.followPath(pathPlanner.computePath(bot, item));
        }
    }
    /**
     * Slightly modified version of the Hunterbot from the pogamut framework*
     */
    float pursueCount = 0;

    private void Pursue(Player target, float pursueLimit) {
        if (lastState != STATE.PURSUE) {
            pursueCount = 0;
            navigation.navigate(target);
        }
        pursueCount += 0.1;
        if (pursueCount > pursueLimit) 
            reset();
    }

    private void AddWeaponAndAmmo(ItemType weaponType, ItemType ammoType, int ammoPacks) {
        AddInventory weapon = new AddInventory();
        AddInventory ammo = new AddInventory();

        weapon.setType(weaponType.getName());
        ammo.setType(ammoType.getName());

        getAct().act(weapon);
        for (int i = 0; i < ammoPacks; i++)
            getAct().act(ammo);
    }

    public static void main(String args[]) throws PogamutException {
        new UT2004BotRunner(GABot.class, "GABoto").setMain(true).startAgent();
    }

    private void ScanMap() {
        for (Item sItem : items.getKnownPickups().values()) {
            if (sItem.getType().getCategory() == ItemType.Category.HEALTH && !m_HealthVialsSpotted.contains(sItem)) {
                log.log(Level.INFO, "{0} : @{1}", new Object[]{sItem.toString(), sItem.getLocation()});
                m_HealthVialsSpotted.add(sItem);
            }

            //currently not used -> tries to change to ammo lists!
            /*if(item.getType().getCategory() == ItemType.Category.AMMO && !m_AmmoSpotted.contains(item))
             if(item.getType() == ItemType.ROCKET_LAUNCHER_AMMO)
             m_AmmoSpotted.add(item);*/

            if (sItem.getType().getCategory() == ItemType.Category.SHIELD && !m_ShieldsSpotted.contains(item)) 
                m_ShieldsSpotted.add(sItem);
            
            if (sItem.getType().getCategory() == ItemType.Category.ADRENALINE && !m_AdrenalineSpotted.contains(item)) 
                m_AdrenalineSpotted.add(sItem);
            
            if (sItem.getType().getCategory() == ItemType.Category.WEAPON && !m_WeaponsSpotted.contains(item)) 
                m_WeaponsSpotted.add(sItem);
        }
    }

    private Item NavigateToNearestItem(ItemType.Category category) {
        if (category == ItemType.Category.ADRENALINE) {
            //body.getCommunication().sendGlobalTextMessage(GetNearest(m_AdrenalineSpotted).toString());
            return GetNearest(m_AdrenalineSpotted);
        }
        if (category == ItemType.Category.SHIELD) {
            //body.getCommunication().sendGlobalTextMessage(GetNearest(m_ShieldsSpotted).toString());
            return GetNearest(m_ShieldsSpotted);
        }
        if (category == ItemType.Category.HEALTH) {
            //body.getCommunication().sendGlobalTextMessage(GetNearest(m_HealthVialsSpotted).getType().toString());
            return GetNearest(m_HealthVialsSpotted);
        }
        if (category == ItemType.Category.WEAPON) {
            //body.getCommunication().sendGlobalTextMessage(GetNearest(m_WeaponsSpotted).toString());
            return GetNearest(m_WeaponsSpotted);
        }

        log.info("Cant find anything in that category");
        return null;
    }

    private Item GetNearestAmmo(ItemType ammoType) {
        List<Item> allOfType = new LinkedList();
        Set<Item> filter;
        //filter = null;

        allOfType.addAll(items.getSpawnedItems(ammoType).values());
        if (allOfType.isEmpty())
            return null;

        filter = tabooItems.filter(allOfType);

      //  if (filter.isEmpty())
       //     body.getCommunication().sendGlobalTextMessage("Ammotype: " + ammoType.getName() + " was not found...");

        return fwMap.getNearestItem(filter, info.getNearestNavPoint());
    }

    private Item GetNearest(ItemType.Category type) {
        List<Item> allOfType = new LinkedList();
        Set<Item> filter;
        filter = null;


        switch (type) {
            case HEALTH:
                allOfType.addAll(items.getSpawnedItems(ItemType.HEALTH_PACK).values());
                if (allOfType.isEmpty())
                    allOfType.addAll(items.getSpawnedItems(ItemType.MINI_HEALTH_PACK).values());
                break;
            //not sure about the difference of shield and armor?
            case SHIELD:
            case ARMOR:
                allOfType.addAll(items.getSpawnedItems(ItemType.SHIELD_PACK).values());
                if (allOfType.isEmpty())
                    allOfType.addAll(items.getSpawnedItems(ItemType.SUPER_SHIELD_PACK).values());
                break;
            case WEAPON:
                allOfType.addAll(items.getSpawnedItems(ItemType.Category.WEAPON).values());
                break;
        }
        filter = tabooItems.filter(allOfType);

        if (filter.isEmpty()) {
            //body.getCommunication().sendGlobalTextMessage("Did not find anything of " + type.name);
        }



        return fwMap.getNearestItem(filter, info.getNearestNavPoint());
    }

    private Item GetNearest(List<Item> lookingFor) {
        List<Item> healths = new LinkedList();
        healths.addAll(items.getSpawnedItems(ItemType.HEALTH_PACK).values());
        if(healths.isEmpty())
            healths.addAll(items.getSpawnedItems(ItemType.MINI_HEALTH_PACK).values());
        
        Set<Item> okHealths = tabooItems.filter(healths);
        
        if(okHealths.isEmpty()){
            //body.getCommunication().sendGlobalTextMessage("no suitable healths...");
            currState = STATE.ROAM;
            return null;
        }

        return fwMap.getNearestItem(okHealths, info.getNearestNavPoint());
    }

    private void Shoot() {
        enemy = players.getNearestVisiblePlayer(players.getVisibleEnemies().values());
        eDist = info.getLocation().getDistance(enemy.getLocation());
        eVel = enemy.getVelocity().size();
        bestWeap = chooseWeapon(eDist, eVel);

        boolean ammoLeft; // Use to check whether the player has ammo left with current weapon preference

        if (enemy != null) 
            if (useImprovedAim)
                ammoLeft = shootWell(bestWeap.item, bestWeap.fireMode);
            else
                shoot.shoot(enemy);
    }

    private boolean shootWell(ItemType weapon, Boolean primary) {
        Location aimAt = enemy.getLocation();
        double projectileSpeed = primary ? weaponry.getWeaponDescriptor(weapon).getPriSpeed() : weaponry.getWeaponDescriptor(weapon).getSecSpeed();
        if (weapon == ItemType.LIGHTNING_GUN) {
            aimAt.z += 30;
        } else if (projectileSpeed != 0) {
            double dist = info.getLocation().getDistance(enemy.getLocation());
            double travelTime = dist / projectileSpeed;
            aimAt = aimAt.add(enemy.getVelocity().asLocation().scale(travelTime));
        }
        if (weapon == ItemType.ROCKET_LAUNCHER) 
            aimAt.z -= 25;
        else if (weapon == ItemType.FLAK_CANNON && !primary) 
            aimAt.z -= 90;
        
        //if(enemy.isCrouched())
        //    aimAt.z -= (CHARACTER_HEIGHT_STANDING - CHARACTER_HEIGHT_CROUCHING);
        return shoot.shoot(weaponry.getWeapon(weapon), primary, aimAt);
    }

    private void ChooseState() {
        float playerDistance;
        boolean canSeePlayer = false;

        enemy = players.getNearestVisiblePlayer(players.getVisibleEnemies().values());
        if (enemy != null)
            canSeePlayer = true;

        if (enemy == null || !canSeePlayer) 
            playerDistance = -1;
        else 
            playerDistance = (float) info.getLocation().getDistance(enemy.getLocation());

        float currentMaxAmmo = weaponry.getWeaponDescriptor(weaponry.getCurrentWeapon().getType()).getPriMaxAmount();
        if (currentMaxAmmo == 0) {
            //body.getCommunication().sendGlobalTextMessage("PROBLEM with currentMaxAmmo! It's 0!?");
            currentMaxAmmo = 10;
        }

        //body.getCommunication().sendGlobalTextMessage("" + (float) weaponry.getCurrentPrimaryAmmo() / (weaponry.getWeaponDescriptor(currentWeapon).getPriMaxAmount()) + " c1: " + m_Gene.getSimpleChromosomeElement(1));
        //body.getCommunication().sendGlobalTextMessage("playerDist: " + playerDistance + "  c3*1500: " + m_Gene.getSimpleChromosomeElement(3)*1500);
        currState = m_Gene.getPhenotype((float) info.getHealth(), (float) (weaponry.getCurrentPrimaryAmmo() / currentMaxAmmo), playerDistance);
        
        //body.getCommunication().sendGlobalTextMessage(""+m_Gene.getChromosomeElement(1) + "; " + (weaponry.getCurrentPrimaryAmmo()) + "; "+(float)(weaponry.getCurrentPrimaryAmmo() / currentMaxAmmo));

        if (!canOverride(currState))
            currState = lastState;
    }

    private List<STATE> InitializeStatePriority() {
        List<STATE> states = new ArrayList<STATE>();
        states.add(STATE.OTHER);
        states.add(STATE.HEALTH);
        states.add(STATE.ENGAGE);
        states.add(STATE.PURSUE);
        states.add(STATE.ROAM);
        states.add(STATE.AMMO);
        return states;
    }

    private boolean canOverride(STATE newState) {
        if (m_States.indexOf(currState) < m_States.indexOf(newState)) 
            return false;

        return true;
    }

    private ItemType EquipWeapon(int i) {

        switch (i) {
            case 0:
                return ItemType.SHIELD_GUN;
            case 1:
                return ItemType.ASSAULT_RIFLE;
            case 2:
                return ItemType.BIO_RIFLE;
            case 3:
                return ItemType.SHOCK_RIFLE;
            case 4:
                return ItemType.LINK_GUN;
            case 5:
                return ItemType.MINIGUN;
            case 6:
                return ItemType.FLAK_CANNON;
            case 7:
                return ItemType.LINK_GUN;
            case 8:
                return ItemType.LIGHTNING_GUN;
            case 9:
                return ItemType.REDEEMER;
            default:
                return weaponry.getCurrentWeapon().getType();
        }
    }

    private void loadNeuralNets() {
        neuralMap.put(new WeapWithFireMode(ItemType.ASSAULT_RIFLE, true), TrainedANN.load("neuralNets/AssaultP.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.BIO_RIFLE, true), TrainedANN.load("neuralNets/BioP.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.SHOCK_RIFLE, true), TrainedANN.load("neuralNets/ShockP.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.SHOCK_RIFLE, false), TrainedANN.load("neuralNets/ShockS.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.LINK_GUN, true), TrainedANN.load("neuralNets/LinkP.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.LINK_GUN, false), TrainedANN.load("neuralNets/LinkS.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.MINIGUN, true), TrainedANN.load("neuralNets/MiniP.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.MINIGUN, false), TrainedANN.load("neuralNets/MiniS.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.FLAK_CANNON, true), TrainedANN.load("neuralNets/FlakP.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.FLAK_CANNON, false), TrainedANN.load("neuralNets/FlakS.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.ROCKET_LAUNCHER, true), TrainedANN.load("neuralNets/RocketP.txt"));
        neuralMap.put(new WeapWithFireMode(ItemType.LIGHTNING_GUN, true), TrainedANN.load("neuralNets/LightP.txt"));
    }

    private WeapWithFireMode chooseWeapon(double distance, double velocity){
        WeapWithFireMode bestWeapon = new WeapWithFireMode(ItemType.SHIELD_GUN, true);
        int bestDmg = Integer.MIN_VALUE;
        int dmg;
        //int wepsEvaluated = 0;
        for (WeapWithFireMode weapon : neuralMap.keySet()){
            if (weaponry.hasAmmoForWeapon(weapon.item)){
                dmg = neuralMap.get(weapon).classifyDamage(distance, velocity);
                if (dmg > bestDmg){
                    bestWeapon = weapon;
                    bestDmg = dmg;
                }
                //wepsEvaluated++;
            }
        }
        try {
            StatsWriter.getInstance().WriteStat(StatsType.WEAPON, this.name+","+bestWeapon.item.getName()+","+distance+","+velocity+","+bestWeapon.fireMode);
        } catch (IOException ex) {
            Logger.getLogger(GABot.class.getName()).log(Level.SEVERE, null, ex);
        }
        return bestWeapon;
    }
    
    private boolean stuckInArea(double areaRadius, int updates){
        if(updateCount == 0){
            stuck = true;
            stuckLoc = info.getLocation();
        }
        else if(stuck)
            stuck = (Location.sub(info.getLocation(), stuckLoc).getLength() < areaRadius);
        updateCount++;
        if(updateCount < updates)
            return false;
        else{
            updateCount = 0;
            if(stuck)
                body.getCommunication().sendGlobalTextMessage(name + " has been detected as STUCK by stuckInArea()");
            return stuck;
        }
    }
}
